package com.naiterui.ehp.bs.recommend.service.impl;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import javax.imageio.ImageIO;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.stereotype.Service;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfDocument;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.draw.LineSeparator;
import com.naiterui.ehp.bp.support.utils.JsonMapper;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;

import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * @author guoyongxiang
 * @date 2019-06-24 15:13
 * @since 1.0.0
 */
@Slf4j
@Service
public class PrescriptionFileService {

    /**
     * 互联网医院的图章
     */
    private static final String HOSPITAL_SEAL_PATH = ConfigUtil.getString("prescription.seal.path");
    /**
     * 普通处方图片路径
     */
    private static final String GENERAL_RECOM_IMG_PATH = ConfigUtil.getString("prescription.general.path");
    /**
     * 儿科处方图片路径
     */
    private static final String GENERAL_RECOM_PD_IMG_PATH =
        ConfigUtil.getString("prescription.pediatric.department.path");
    /**
     * 互联网医院图章（颜色加深）
     */
    private static final String HOSPITAL_SEAL_PATH_BRIGHT = ConfigUtil.getString("prescription.seal.bright.path");

    /**
     * 互联网医院图章（颜色加深）
     */
    private static final String PDF_BACKGROUND = ConfigUtil.getString("prescription.background.path");

    private static final int MAX_SKU_QUANTITY = 5;

    BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
    BaseFont bfEnglish = BaseFont.createFont(BaseFont.HELVETICA, "", BaseFont.NOT_EMBEDDED);

    Font firsetTitleFont = new Font(bfChinese, 18, Font.BOLD, BaseColor.BLACK); // 一级标题

    Font secondTitleFont = new Font(bfChinese, 15, Font.BOLD, BaseColor.BLACK); // 二级标题

    Font secondTitleFontEn = new Font(bfEnglish, 18, Font.BOLD, BaseColor.BLACK); // 二级标题-EN

    Font fontContent = new Font(bfChinese, 10.5f, Font.NORMAL, BaseColor.BLACK);

    Font fontContent2 = new Font(bfChinese, 10.5f, Font.NORMAL, BaseColor.DARK_GRAY);

    Font fontContentRed = new Font(bfChinese, 10.5f, Font.NORMAL, BaseColor.RED);

    Font fontContentBOLD = new Font(bfChinese, 10, Font.BOLD, BaseColor.BLACK);

    Font fontContentEn = new Font(bfEnglish, 10, Font.NORMAL, BaseColor.BLACK);

    LineSeparator line = new LineSeparator(1f, 100, BaseColor.BLACK, Element.ALIGN_CENTER, -3f);

    LineSeparator line2 = new LineSeparator(1f, 90, BaseColor.BLACK, Element.ALIGN_CENTER, -2f);

    LineSeparator line3 = new LineSeparator(1f, 90, BaseColor.BLACK, Element.ALIGN_CENTER, 0f);

    /**
     * 空格占位
     */
    public static final String FOUR_BLANK_STR = "\u00a0\u00a0\u00a0\u00a0";
    public static final String BLANK_STRS = "\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0"
        + "\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0";

    public PrescriptionFileService() throws IOException, DocumentException {}

    /**
     * 生成PDF文件
     *
     * @param path
     * @throws IOException
     * @throws DocumentException
     */
    public void generatePDF(String path, PrescriptionInfo prescriptionInfo)
        throws IOException, DocumentException, ReflectiveOperationException {
        long start = System.currentTimeMillis();
        File file = new File(path);
        FileUtils.forceMkdirParent(file);
        FileOutputStream fos = new FileOutputStream(path);
        this.generatePDF(fos, prescriptionInfo);
        fos.close();
        log.info("处方PDF生成完成 cost:{}ms, serialNumber:{}, path:{}", System.currentTimeMillis() - start,
            prescriptionInfo.getSerialNumber(), path);
    }

    /**
     * 生成PNG图片
     *
     * @param path
     * @throws IOException
     * @throws DocumentException
     */
    public void generateImg(String path, PrescriptionInfo prescriptionInfo)
        throws IOException, DocumentException, ReflectiveOperationException {
        long start = System.currentTimeMillis();
        File file = new File(path);
        FileUtils.forceMkdirParent(file);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        this.generatePDF(baos, prescriptionInfo);
        PDDocument pdDocument = PDDocument.load(baos.toByteArray());
        PDFRenderer pdfRenderer = new PDFRenderer(pdDocument);
        BufferedImage image = pdfRenderer.renderImageWithDPI(0, 150, ImageType.RGB);
        ImageIO.write(image, "PNG", new File(path));
        pdDocument.close();
        baos.close();
        log.info("处方PNG生成完成 cost:{}ms serialNumber:{}, path:{}", System.currentTimeMillis() - start,
            prescriptionInfo.getSerialNumber(), path);
    }

    private void generatePDF(OutputStream ouput, PrescriptionInfo prescriptionInfo)
        throws DocumentException, IOException, ReflectiveOperationException {
        log.info("生成处方签名原始文件，业务数据内容 {}", JsonMapper.toJson(prescriptionInfo));
        // 页面大小
        Rectangle rect = new Rectangle(PageSize.A4);
        boolean isChild = prescriptionInfo.getPatientAge() < 15;
        String fileRecomPath = GENERAL_RECOM_IMG_PATH;
        if (isChild) {
            fileRecomPath = GENERAL_RECOM_PD_IMG_PATH;
            rect.setBackgroundColor(new BaseColor(216, 228, 188));
        }
        Document document = new Document(rect);
        // 创建PdfWriter对象
        PdfWriter writer = PdfWriter.getInstance(document, ouput);
        // 打开文档
        document.open();
        // 页边空白
        document.setMargins(20, 20, 30, 40);
        // 背景图片
        /*if (isChild) {
            File background = new File(PDF_BACKGROUND);
            if (background.exists()) {
                Image image = Image.getInstance(PDF_BACKGROUND);
                image.setAbsolutePosition(0, 0);
                image.scaleToFit(2000F, 2000F);
                document.add(image);
            }
        }*/
        // 生成表头
        this.generatePDFHerder(document, prescriptionInfo, fileRecomPath, isChild);
        // 药品列表
        for (int i = 0; i < MAX_SKU_QUANTITY; i++) {
            List<PrescriptionSku> skus = prescriptionInfo.getSkus();
            if (skus.size() > i) {
                if (currentHeight(writer) > 500) {
                    // 生成表尾
                    this.generatePDFTail(document, writer);
                    document.newPage();
                    // 生成表头
                    this.generatePDFHerder(document, prescriptionInfo, fileRecomPath, isChild);
                }
                PrescriptionSku skuObj = skus.get(i);
                StringBuilder usegesBuilder = new StringBuilder("用法用量:");
                if (StringUtils.isNotBlank(skuObj.getUseges())) {
                    if (skuObj.getUseges().indexOf("周期") != -1) {
                        usegesBuilder.append(skuObj.getUseges(), 0, skuObj.getUseges().lastIndexOf("周期"));
                    } else {
                        usegesBuilder.append(skuObj.getUseges());
                    }
                }
                StringBuilder quantityBuilder = new StringBuilder();
                quantityBuilder.append(skuObj.getQuantity());
                if (StringUtils.isNotBlank(skuObj.getQuantityUnit())) {
                    quantityBuilder.append(skuObj.getQuantityUnit());
                } else {
                    quantityBuilder.append("盒");
                }
                // {药品名称} X{数量}
                Phrase sku = this.buildPhraseNormal((i + 1) + "、" + skuObj.getName(), " ", fontContent, fontContent);
                Phrase quantity = this.buildPhraseNormal("X ", quantityBuilder.toString(), fontContent, fontContent);
                document.add(this.buildTable(new float[] {0.9f, 0.1f}, 90, 10, 3, sku, quantity));

                Phrase useges = this.buildPhraseNormal(usegesBuilder.toString(), " ", fontContent, fontContent);
                document.add(this.buildTable(new float[] {0.9f}, 90, 3, 10, useges));
                document.add(line3);
                if (i == skus.size() - 1) {
                    Phrase blank = this.buildPhraseNormal("以下空白", " ", fontContent, fontContent);
                    document.add(this.buildTable(new float[] {0.9f}, 90, 10, 5, blank));
                }
            } else {
                // 占位
                Phrase sku = this.buildPhraseNormal(" ", " ", fontContent, fontContent);
                Phrase quantity = this.buildPhraseNormal(" ", " ", fontContent, fontContent);
                document.add(this.buildTable(new float[] {0.9f, 0.1f}, 90, 10, 3, sku, quantity));
                Phrase useges = this.buildPhraseNormal(" ", " ", fontContent, fontContent);
                document.add(this.buildTable(new float[] {0.9f}, 90, 3, 10, useges));
            }
        }
        // 生成表尾
        this.generatePDFTail(document, writer);
        document.close();
        writer.close();
    }

    public static float currentHeight(PdfWriter writer) throws ReflectiveOperationException {
        Method getPdfDocument = writer.getClass().getDeclaredMethod("getPdfDocument");
        getPdfDocument.setAccessible(true);
        PdfDocument pdfD = (PdfDocument)getPdfDocument.invoke(writer);
        Field getHeight = pdfD.getClass().getDeclaredField("currentHeight");
        getHeight.setAccessible(true);
        return (float)getHeight.get(pdfD);
    }

    /**
     * 生成pdf头
     *
     * @param document
     * @param prescriptionInfo
     * @param fileRecomPath
     * @param isChild
     * @throws DocumentException
     * @throws IOException
     */
    private void generatePDFHerder(Document document, PrescriptionInfo prescriptionInfo, String fileRecomPath,
        boolean isChild) throws DocumentException, IOException {
        Paragraph titleRow = new Paragraph(prescriptionInfo.getHospitalName(), firsetTitleFont);
        Paragraph titleRow2 = new Paragraph("处方笺", firsetTitleFont);
        titleRow.setAlignment(PdfContentByte.ALIGN_CENTER);
        titleRow.setSpacingAfter(10);
        titleRow2.setAlignment(PdfContentByte.ALIGN_CENTER);
        titleRow2.setSpacingAfter(20);
        document.add(titleRow);
        document.add(titleRow2);

        // 设置普通处方图片位置
        File file1 = new File(fileRecomPath);
        if (file1.exists()) {
            Image image = Image.getInstance(fileRecomPath);
            if (isChild) {
                image.setAbsolutePosition(470, 750);
                image.scaleToFit(50, 75);
            } else {
                image.setAbsolutePosition(450, 720);
                image.scaleToFit(100, 150);
            }
            document.add(image);
        } else {
            // LOGGER.warn("普通处方图片路径不存在，请检查图片路径目录 {}", fileRecomPath);
        }

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd");
        String prescriptionTime = dateFormat.format(prescriptionInfo.getCreatedAt());

        // 第一行
        Phrase sn = this.buildPhraseNormal("NO：", prescriptionInfo.getSerialNumber(), fontContent, fontContentEn);
        Phrase time = this.buildPhraseNormal("开方日期：", prescriptionTime, fontContent, fontContent);
        document.add(this.buildTable(new float[] {0.7f, 0.3f}, 95, 10, 10, sn, time));
        // 分割线
        document.add(line);
        // 第二行
        Phrase name = this.buildPhrase("姓名：", prescriptionInfo.getPatientName(), fontContent, fontContent);
        String genderStr = prescriptionInfo.getPatientGender() == 0 ? "女" : "男";
        Phrase gender = this.buildPhrase("性别：", genderStr, fontContent, fontContent);
        Phrase age = this.buildPhrase("年龄：", prescriptionInfo.getPatientAgeStr(), fontContent, fontContent);
        document.add(this.buildTable(new float[] {0.4f, 0.3f, 0.3f}, 95, 10, 10, name, gender, age));

        Phrase caseId =
            this.buildPhrase("病历号：", String.valueOf(prescriptionInfo.getInquirerId()), fontContent, fontContentEn);
        Phrase costType = this.buildPhrase("费别：", "自费", fontContent, fontContentRed);
        Phrase department = this.buildPhrase("科室：", prescriptionInfo.getDepartmentName(), fontContent, fontContent);
        document.add(this.buildTable(new float[] {0.4f, 0.3f, 0.3f}, 95, 10, 10, caseId, costType, department));

        // 第四行
        Phrase diagnosis = this.buildPhrase("临床诊断：", prescriptionInfo.getDiagnosis(), fontContent, fontContent);
        Phrase phone = this.buildPhrase("电话：", StringUtils.defaultIfBlank(prescriptionInfo.getPatientPhone(), "无"),
            fontContent, fontContent);
        document.add(this.buildTable(new float[] {0.7f, 0.3f}, 95, 10, 10, diagnosis, phone));
        // 分割线
        document.add(line);

        // 第七行-RP
        Paragraph rp = new Paragraph("Rp.", secondTitleFontEn);
        rp.setSpacingBefore(10);
        rp.setSpacingAfter(5);
        rp.setIndentationLeft(15);
        document.add(rp);

        // 药品分割线
        Phrase line2Phrase = new Phrase();
        line2Phrase.add(line);
        line2Phrase.setLeading(5);
    }

    /**
     * 生成pdf尾
     *
     * @param document
     * @throws DocumentException
     * @throws IOException
     */
    private void generatePDFTail(Document document, PdfWriter writer) throws DocumentException, IOException {
        // 签名区域分割线
        Phrase linePhrase = new Phrase();
        linePhrase.add(line);
        PdfPTable pdfPTable = this.buildTable(new float[] {1f}, 95, 15, 10, linePhrase);
        pdfPTable.setTotalWidth(525f);
        pdfPTable.writeSelectedRows(0, -1, 30, 180, writer.getDirectContent());
        // 医生药师签名区域
        Phrase dr = this.buildPhrase("医师：", "", fontContent, fontContent);
        Phrase pharmacist = this.buildPhrase("审核药师：", "", fontContent, fontContent);
        PdfPTable pdfPTable1 = this.buildTable(new float[] {0.7f, 0.3f}, 95, 15, 10, dr, pharmacist);
        pdfPTable1.setTotalWidth(500f);
        pdfPTable1.writeSelectedRows(0, -1, 40, 150, writer.getDirectContent());

        // 互联网医院图章
        File file = new File(HOSPITAL_SEAL_PATH);
        if (file.exists()) {
            Image image = Image.getInstance(HOSPITAL_SEAL_PATH);
            image.setAbsolutePosition(400, 50);
            image.scaleToFit(150, 150);
            document.add(image);
        } else {
            log.warn("互联网医院处方图章不存在，请检查图章文件目录 {}", HOSPITAL_SEAL_PATH_BRIGHT);
        }

        // 调配金额区域
        Phrase check = this.buildPhrase("调配/发药：", " ", fontContent, fontContent);
        Phrase price = this.buildPhrase("药品金额：", "", fontContent, fontContentEn);
        PdfPTable table2 = this.buildTable(new float[] {0.7f, 0.3f}, 95, 10, 10, check, price);
        table2.setTotalWidth(500f);
        table2.writeSelectedRows(0, -1, 40, 110, writer.getDirectContent());

        // 底部分割线
        Phrase line2Phrase = new Phrase();
        line2Phrase.add(line);
        PdfPTable table3 = this.buildTable(new float[] {1f}, 95, 15, 10, line2Phrase);
        table3.setTotalWidth(525f);
        table3.writeSelectedRows(0, -1, 30, 100, writer.getDirectContent());
        Paragraph tips = new Paragraph(25f, "未经授权不得自行下载使用，本处方最终解释权归本院。", fontContent2);
        Paragraph tips2 = new Paragraph("根据《医疗机构药事管理规定》，为保障患者用药安全，除药品质量原因外，药品一经发出不得退换，门诊", fontContent2);
        Paragraph tips3 = new Paragraph("处方笺24小时内有效。", fontContent2);
        PdfPTable table4 = this.buildTable(new float[] {1f}, 95, 10, 10, tips, tips2, tips3);
        table4.setTotalWidth(500f);
        table4.writeSelectedRows(0, -1, 40, 80, writer.getDirectContent());
    }

    private Phrase buildPhrase(String title, String content, Font titleFont, Font contentFont) {
        Phrase phrase = new Phrase();
        Chunk chunkTitle = new Chunk(title, titleFont);
        Chunk chunkContent;
        if (StringUtils.isBlank(content)) {
            log.info("没有内容title={},content={}", title, content);
            // 内容为空
            chunkContent = new Chunk(BLANK_STRS, contentFont);
        } else {
            log.info("有内容title={},content={}", title, content);
            // 内容不为空
            chunkContent = new Chunk(FOUR_BLANK_STR + content + FOUR_BLANK_STR, contentFont);
        }
        phrase.add(chunkTitle);
        // 内容添加下划线
        chunkContent.setUnderline(0.5f, -1.5f);
        phrase.add(chunkContent);
        return phrase;
    }

    private PdfPCell buildCell(Phrase phrase) {
        PdfPCell pdfPCell = new PdfPCell(phrase);
        // 隐藏单元格周边的上边框
        pdfPCell.disableBorderSide(Rectangle.TOP);
        // 隐藏单元格周边的下边框
        pdfPCell.disableBorderSide(Rectangle.BOTTOM);
        // 隐藏单元格周边的左边框
        pdfPCell.disableBorderSide(Rectangle.LEFT);
        // 隐藏单元格周边的右框
        pdfPCell.disableBorderSide(Rectangle.RIGHT);
        return pdfPCell;
    }

    private PdfPTable buildTable(float[] relativeWidths, int widthPercentage, int pacingBefore, int pacingAfter,
        Phrase... phrases) {
        PdfPTable pdfPTable = new PdfPTable(relativeWidths);
        pdfPTable.setWidthPercentage(widthPercentage);
        for (Phrase phrase : phrases) {
            pdfPTable.addCell(this.buildCell(phrase));
        }
        pdfPTable.setSpacingBefore(pacingBefore);
        pdfPTable.setSpacingAfter(pacingAfter);
        return pdfPTable;
    }

    private Phrase buildPhraseNormal(String title, String content, Font titleFont, Font contentFont) {
        Phrase phrase = new Phrase();
        Chunk chunkTitle = new Chunk(title, titleFont);
        Chunk chunkContent = new Chunk(content, contentFont);
        phrase.add(chunkTitle);
        phrase.add(chunkContent);
        return phrase;
    }

    @Data
    public static class PrescriptionInfo {

        /**
         * 处方编号
         */
        private String serialNumber;
        /**
         * 诊断
         */
        private String diagnosis;
        /**
         * 医生姓名
         */
        private String doctorName;
        /**
         * 患者姓名
         */
        private String patientName;
        /**
         * 患者年龄
         */
        private Integer patientAge;
        /**
         * 患者年龄单位
         */
        private String patientAgeUnit;
        /**
         * 患者显示年龄
         */
        private String patientAgeStr;
        /**
         * 患者性别
         */
        private Integer patientGender;

        /**
         * 医生医院
         */
        private String hospitalName;
        /**
         * 医生科室
         */
        private String departmentName;
        /**
         * 药师姓名
         */
        private String pharmacistName;
        /**
         * 提示
         */
        private String tips;
        /**
         * 开方时间
         */
        private Date createdAt;
        /**
         * 药品列表
         */
        private List<PrescriptionSku> skus;
        /**
         * 就诊人id
         */
        private Long inquirerId;
        /**
         * 开方时间
         */
        private String patientPhone;

    }

    @Builder
    @Data
    public static class PrescriptionSku {
        /**
         * 商品名(厂商 通用名 规格)
         */
        private String name;
        /**
         * 数量
         */
        private Integer quantity;
        /**
         * 用法用量
         */
        private String useges;
        /**
         * 备注
         */
        private String backup;
        /**
         * 包装单位：瓶，盒，袋，包
         */
        private String quantityUnit;
    }

}
